﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Core.Entities;
using Core.Enums;
using Core.Writer;
using System.Collections;
using Core.Tools;

namespace Core.ReadWriter
{
    [Serializable]
    public class ListReadWriter : BaseReadWriter
    {
        private static readonly Dictionary<Type, Type> ValueTypeBuffer;
        private static readonly object ValueTypeBufferLock;

        static ListReadWriter()
        {
            ValueTypeBuffer = new Dictionary<Type, Type>();
            ValueTypeBufferLock = new object();
        }

        public static bool TryGetValueType(Type Type, out Type ValueType)
        {
            if (ValueTypeBuffer.TryGetValue(Type, out ValueType))
            {
                return ValueType != null;
            }

            lock (ValueTypeBufferLock)
            {
                if (ValueTypeBuffer.TryGetValue(Type, out ValueType))
                {
                    return ValueType != null;
                }

                ValueType = GetIListType(Type);

                if (ValueType != null)
                {
                    ValueType = ValueType.GetGenericArguments()[0];
                }

                ValueTypeBuffer.Add(Type, ValueType);


                return ValueType != null;
            }
        }

        private static Type GetIListType(Type Type)
        {
            if (Type.IsInterface && Type.IsGenericType && Type.GetGenericTypeDefinition() == typeof(IList<>))
            {
                return Type;
            }
            else
            {
                var Interfaces = Type.GetInterfaces();

                foreach (var Item in Interfaces)
                {
                    if (Item.IsGenericType && Item.GetGenericTypeDefinition() == typeof(IList<>))
                    {
                        return Item;
                    }
                }
            }

            return null;
        }
        
        private readonly Type Type;

        private Type ValueType;

        private IList IListContent
        {
            get
            {
                return (IList)InternalContent;
            }
        }

        internal ListReadWriter(Type Type, object Content, Type ValueType, RWSigns Sign) : base(Content, Sign)
        {
            this.Type = Type;
            this.ValueType = ValueType;

            Reset();
        }

        public ListReadWriter(IList Content, Type Type = null, RWSigns Sign = RWSigns.ChangeType | RWSigns.Property) : base(Content, Sign)
        {
            if (Content == null)
            {
                throw new NullReferenceException("Content");
            }

            if (Type == null)
            {
                Type = Content.GetType();
            }

            this.Type = Type;

            if (!TryGetValueType(Type, out ValueType))
            {
                ValueType = typeof(object);
            }
        }

        public override object this[string Name]
        {
            get
            {
                int Index;

                if (int.TryParse(Name, out Index))
                {
                    return this[Index];
                }

                throw new FormatException("Name");
            }
            set
            {
                int Index;

                if (int.TryParse(Name, out Index))
                {
                    this[Index] = value;

                    return;
                }

                throw new FormatException("Name");
            }
        }

        public override object this[int Index]
        {
            get
            {
                return AsGetValue(IListContent[Index]);
            }
            set
            {
                object Value = AsSetValue(value);

                if ((Sign & RWSigns.ChangeType) != 0 && !ValueType.IsInstanceOfType(Value))
                {
                    Value = Convert.ChangeType(Value, ValueType);
                }

                if (Index == Count)
                {
                    IListContent.Add(Value);
                }
                else
                {
                    IListContent[Index] = Value;
                }
            }
        }

        public override IEnumerable<string> Fields
        {
            get
            {
                return new CollectionEnumerable<string>(Index =>
                {
                    if (Index >= 0 && Index < Count)
                    {
                        return Index.ToString();
                    }

                    throw new IndexOutOfRangeException("Index");
                }, Count, true);
            }
        }

        public override int Count
        {
            get
            {
                return IListContent.Count;
            }
        }

        public override RWTypes ObjectType
        {
            get
            {
                return RWTypes.Array;
            }
            set
            {
                if (value != RWTypes.Array)
                {
                    throw new NotSupportedException("ListReadWriter ObjectType only be array.");
                }
            }
        }

        public override void Add(string Name, object Value)
        {
            Value = AsSetValue(Value);

            if ((Sign & RWSigns.ChangeType) != 0 && !ValueType.IsInstanceOfType(Value))
            {
                Value = Convert.ChangeType(Value, ValueType);
            }

            IListContent.Add(Value);
        }

        public override IDataWriter CreateChildrenWriter(CreateChildrenWriterInfo Info)
        {
            if (ValueType != typeof(object))
            {
                return Create(ValueType, Sign);
            }

            return base.CreateChildrenWriter(Info);
        }

        public override bool TryGetValue(string Name, out object Value)
        {
            int Index;

            if (!int.TryParse(Name, out Index))
            {
                Value = null;

                return false;
            }

            if (Index >= 0 && Index < Count)
            {
                Value = IListContent[Index];

                return true;
            }

            Value = null;

            return false;
        }

        public override object Content
        {
            get
            {
                if (Type.IsInstanceOfType(InternalContent))
                {
                    return InternalContent;
                }

                if (InternalContent is MultidimensionalArrayList)
                {
                    return ((MultidimensionalArrayList)InternalContent).Content;
                }

                if (Type.IsArray)
                {
                    return GetArrayContent();
                }

                return Convert.ChangeType(InternalContent, Type);
            }
        }

        private unsafe object GetArrayContent()
        {
            int Rank = Type.GetArrayRank();

            int* Lengths = stackalloc int[Rank];

            SetLengths(Lengths, Rank, IListContent);

            var Result = ArrayHelper.InternalCreate(Type.GetElementType(), Rank, Lengths);

            SetValues(Result, 0, Rank, Lengths, IListContent);

            return Result;
        }

        private unsafe static void SetLengths(int* Lengths, int Rank, IList List)
        {
            if (*Lengths < List.Count)
            {
                *Lengths = List.Count;
            }

            if (Rank != 1)
            {
                ++Lengths;
                --Rank;

                foreach (IList Item in List)
                {
                    SetLengths(Lengths, Rank, Item);
                }
            }
        }

        private unsafe static void SetValues(Array Array, int RankIndex, int Rank, int* Indices, IList Values)
        {
            int Flag = RankIndex + 1;

            if (Flag == Rank)
            {
                for (int i = 0; i < Values.Count; i++)
                {
                    Indices[RankIndex] = i;

                    ArrayHelper.SetValue(Array, Rank, Indices, Values[i]);
                }
            }
            else
            {
                for (int i = 0; i < Values.Count; i++)
                {
                    Indices[RankIndex] = i;

                    SetValues(Array, Flag, Rank, Indices, (IList)Values[i]);
                }
            }
        }
    }
}
